import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.Arrays;
import java.util.stream.Stream;

/**
 * 矩阵置零
 * 题目：给定一个m x n 的矩阵，如果一个元素为 0，则将其所在行和列的所有元素都设为 0。请使用原地算法。
 * <p>
 * 示例1:
 * 输入:
 * [
 * [1,1,1],
 * [1,0,1],
 * [1,1,1]
 * ]
 * 输出:
 * [
 * [1,0,1],
 * [0,0,0],
 * [1,0,1]
 * ]
 * <p>
 * 示例2:
 * 输入:
 * [
 * [0,1,2,0],
 * [3,4,5,2],
 * [1,3,1,5]
 * ]
 * 输出:
 * [
 * [0,0,0,0],
 * [0,4,5,0],
 * [0,3,1,0]
 * ]
 * 进阶:
 * <p>
 * 一个直接的解决方案是使用 O(mn)的额外空间，但这并不是一个好的解决方案。
 * 一个简单的改进方案是使用 O(m+n) 的额外空间，但这仍然不是最好的解决方案。
 * 你能想出一个常数空间的解决方案吗？
 * <p>
 * 来源：力扣（LeetCode-73）
 * 链接：https://leetcode-cn.com/problems/set-matrix-zeroes
 *
 * @author godfrey
 * @since 2020-11-12
 */
@DisplayName("矩阵置零")
public class SetMatrixZeroes extends BaseTest {

    static Stream<Arguments> testArguments() {
        return Stream.of(
                Arguments.arguments((Object) new int[][]{
                        {1, 1, 1},
                        {1, 0, 1},
                        {1, 1, 1}}),
                Arguments.arguments((Object) new int[][]{
                        {0, 1, 2, 0},
                        {3, 4, 5, 2},
                        {1, 3, 1, 5}})
        );
    }

    @DisplayName("标记法-时间复杂度O(m*n)，空间复杂度O(m+n)")
    @ParameterizedTest
    @MethodSource("testArguments")
    void setZeroes(int[][] matrix) {
        final int m = matrix.length;
        final int n = matrix[0].length;
        //标记行是否是0
        boolean[] row = new boolean[m];
        //标记列是否是0
        boolean[] col = new boolean[n];
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (matrix[i][j] == 0) {
                    row[i] = col[j] = true;
                }
            }
        }

        for (int i = 0; i < m; ++i) {
            if (row[i]) {
                Arrays.fill(matrix[i], 0);
            }
        }

        for (int i = 0; i < n; ++i) {
            if (col[i]) {
                for (int j = 0; j < m; ++j) {
                    matrix[j][i] = 0;
                }
            }
        }
    }

    @DisplayName("标记法-时间复杂度O(m*n)，空间复杂度O(1)")
    @ParameterizedTest
    @MethodSource("testArguments")
    void setZeroes2(int[][] matrix) {
        final int m = matrix.length;
        final int n = matrix[0].length;
        //第一行是否存在0
        boolean rowHasZero = false;
        //第一列是否存在0
        boolean colHasZero = false;

        //检查第一行是否为零
        for (int i = 0; i < n; ++i) {
            if (matrix[0][i] == 0) {
                rowHasZero = true;
                break;
            }
        }
        //检查第一列是否为零
        for (int i = 0; i < m; ++i) {
            if (matrix[i][0] == 0) {
                colHasZero = true;
                break;
            }
        }
        //把第一行、第一列作为标志位
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (matrix[i][j] == 0) {
                    matrix[0][j] = 0;
                    matrix[i][0] = 0;
                }
            }
        }
        //置0
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (matrix[i][0] == 0 || matrix[0][j] == 0) {
                    matrix[i][j] = 0;
                }
            }
        }
        //第一行置0
        if (rowHasZero) {
            for (int i = 0; i < n; i++) {
                matrix[0][i] = 0;
            }
        }
        //第一列置0
        if (colHasZero) {
            for (int i = 0; i < m; i++) {
                matrix[i][0] = 0;
            }
        }
    }
}
